交互式 Choropleth 地图

交互式 Choropleth 地图

使用 GeoJSON 和一些自定义控件创建各地区人口密度的彩色交互式等值线图的案例研究。新闻网站会很喜欢用这个。

这是一个在 GeoJSON 和一些自定义控件的帮助下创建美国各州人口密度的彩色交互式等值线图的案例研究(希望能说服所有尚未使用 Leaflet 的主要新闻和政府网站开始这样做) 本教程的灵感来自由 Ryan Murphy 创建的德克萨斯论坛报美国参议院径流结果地图(也由 Leaflet 提供支持)。

数据源

我们将创建美国各州人口密度的可视化。由于数据量(状态形状和每个状态的密度值)不是很大,最方便和最简单的存储和显示方式是 GeoJSON。 我们的 GeoJSON 数据 (us-states.js) 的每个特征将如下所示:

{
    "type": "Feature",
    "properties": {
        "name": "Alabama",
        "density": 94.65
    },
    "geometry": ...
    ...
}

具有状态形状的 GeoJSON 由 D3 成名的 Mike Bostock 分享,并根据美国人口普查局 2011 年 7 月 1 日的数据对这篇维基百科文章中的密度值进行了扩展,并分配给 statesData JS 变量。

基本州地图

让我们在地图上显示我们的状态数据:

var map = L.map('map').setView([37.8, -96], 4);

var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);

L.geoJson(statesData).addTo(map);

添加一些颜色 现在我们需要根据人口密度为各州着色。为地图选择漂亮的颜色可能很棘手,但有一个很棒的工具可以帮助解决这个问题——ColorBrewer。

使用我们从中获得的值,我们创建一个函数,该函数根据人口密度返回颜色:

function getColor(d) {
    return d > 1000 ? '#800026' :
           d > 500  ? '#BD0026' :
           d > 200  ? '#E31A1C' :
           d > 100  ? '#FC4E2A' :
           d > 50   ? '#FD8D3C' :
           d > 20   ? '#FEB24C' :
           d > 10   ? '#FED976' :
                      '#FFEDA0';
}

接下来,我们为 GeoJSON 层定义一个样式函数,使其 fillColor 取决于 feature.properties.density 属性,同时稍微调整外观并用虚线添加漂亮的触感。

function style(feature) {
    return {
        fillColor: getColor(feature.properties.density),
        weight: 2,
        opacity: 1,
        color: 'white',
        dashArray: '3',
        fillOpacity: 0.7
    };
}

L.geoJson(statesData, {style: style}).addTo(map);

现在看起来好多了!

添加交互 现在让我们在鼠标悬停时以某种方式突出显示状态。首先,我们将为 layer mouseover 事件定义一个事件监听器:

function highlightFeature(e) {
    var layer = e.target;

    layer.setStyle({
        weight: 5,
        color: '#666',
        dashArray: '',
        fillOpacity: 0.7
    });

    if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
        layer.bringToFront();
    }
}

在这里,我们可以访问通过 e.target 悬停的图层,在图层上设置一个粗灰色边框作为我们的高亮效果,也将它带到前面,以便边框不会与附近的状态发生冲突(但不适用于 IE , Opera 或 Edge,因为他们在鼠标悬停时会遇到问题)。 接下来我们将定义 mouseout 会发生什么:

function resetHighlight(e) {
    geojson.resetStyle(e.target);
}

方便的 geojson.resetStyle 方法会将图层样式重置为其默认状态(由我们的样式函数定义)。为此,请确保我们的 GeoJSON 层可通过 geojson 变量访问,方法是在我们的侦听器之前定义它并稍后将层分配给它:

var geojson;
// ... our listeners
geojson = L.geoJson(...);

作为补充,让我们定义一个缩放到状态的点击监听器:

function zoomToFeature(e) {
    map.fitBounds(e.target.getBounds());
}

现在我们将使用 onEachFeature 选项在我们的状态层上添加监听器:

function onEachFeature(feature, layer) {
    layer.on({
        mouseover: highlightFeature,
        mouseout: resetHighlight,
        click: zoomToFeature
    });
}

geojson = L.geoJson(statesData, {
    style: style,
    onEachFeature: onEachFeature
}).addTo(map);

这使得状态在悬停时很好地突出显示,并使我们能够在侦听器中添加其他交互。 自定义信息控制 我们可以使用通常的点击弹出窗口来显示有关不同状态的信息,但我们将选择不同的路线——在自定义控件内的状态悬停时显示它。 这是我们控制的代码:

var info = L.control();

info.onAdd = function (map) {
    this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
    this.update();
    return this._div;
};

// method that we will use to update the control based on feature properties passed
info.update = function (props) {
    this._div.innerHTML = '<h4>US Population Density</h4>' +  (props ?
        '<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>'
        : 'Hover over a state');
};

info.addTo(map);

当用户将鼠标悬停在某个状态上时,我们需要更新控件,因此我们还将修改我们的侦听器,如下所示:

function highlightFeature(e) {
    ...
    info.update(layer.feature.properties);
}

function resetHighlight(e) {
    ...
    info.update();
}

该控件还需要一些 CSS 样式才能看起来不错:

.info {
    padding: 6px 8px;
    font: 14px/16px Arial, Helvetica, sans-serif;
    background: white;
    background: rgba(255,255,255,0.8);
    box-shadow: 0 0 15px rgba(0,0,0,0.2);
    border-radius: 5px;
}
.info h4 {
    margin: 0 0 5px;
    color: #777;
}

自定义图例控件 创建带有图例的控件更容易,因为它是静态的并且在状态悬停时不会改变。 JavaScript 代码:

var legend = L.control({position: 'bottomright'});

legend.onAdd = function (map) {

    var div = L.DomUtil.create('div', 'info legend'),
        grades = [0, 10, 20, 50, 100, 200, 500, 1000],
        labels = [];

    // loop through our density intervals and generate a label with a colored square for each interval
    for (var i = 0; i < grades.length; i++) {
        div.innerHTML +=
            '<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
            grades[i] + (grades[i + 1] ? '&ndash;' + grades[i + 1] + '<br>' : '+');
    }

    return div;
};

legend.addTo(map);

控件的 CSS 样式(我们还重用了前面定义的 info 类):

.legend {
    line-height: 18px;
    color: #555;
}
.legend i {
    width: 18px;
    height: 18px;
    float: left;
    margin-right: 8px;
    opacity: 0.7;
}
0 0 投票数
文章评分
订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x